iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
自我挑戰組

攜手 AI 從零開始打造一款 Flutter 應用程式系列 第 5

Day 5: UI 佈局基礎 - Row, Column, Container

  • 分享至 

  • xImage
  •  

前言

大家好!我們昨天逐行解構了 Flutter 的預設 App,徹底搞懂了 MaterialAppScaffold 等核心骨架。今天,我們終於要告別那支藍色的計數器,捲起袖子,作為一名建築師,從一片空白的畫布上,親手搭建屬於「省錢拍拍 (SnapSaver)」的第一個畫面。

今天的任務,掌握 Flutter UI 佈局的「三劍客」:

  1. Container: 萬能容器,用來設定背景、邊距、大小。
  2. Column: 垂直佈局,讓元件由上到下排列。
  3. Row: 水平佈局,讓元件由左到右排列。

同時,釐清 Flutter 佈局中最重要的核心觀念:主軸 (Main Axis) 與 交錯軸 (Cross Axis)。

Step 1: 準備一張乾淨的畫布

首先要對 lib/main.dart 進行改造。

  1. 刪除 MyHomePage_MyHomePageState這兩個 class。
  2. 用下面這段更簡潔的程式碼完全取代 lib/main.dart 的內容。
// lib/main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 關閉右上角的 Debug 標籤
      debugShowCheckedModeBanner: false,
      title: 'SnapSaver',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

// 這是我們今天主要的工作區域
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('省錢拍拍 (SnapSaver)'),
        backgroundColor: Colors.deepPurple.shade200, // 給 AppBar 一點顏色
      ),
      // body 中心放置一個文字,作為起點
      body: const Center(
        child: Text('Let\'s start building!'),
      ),
    );
  }
}

重新運行 App,你會看到一個乾淨、清爽的頁面。這就是我們的起點。

初次建立

Step 2: Container - 什麼都能裝的萬能容器

Container 是 Flutter 中常用的 Widget 之一,可以把它想像成是網頁開發中的div。它本身是透明看不見的,但可以賦予它各種屬性,讓它成為可見的畫布或空間佔位符。

HomePagebody 修改成:

body: Center(
  child: Container(
    width: 200,
    height: 200,
    // padding: 內邊距,Container 內部 child 與邊界的距離
    padding: const EdgeInsets.all(16.0),
    // margin: 外邊距,Container 與外部其他元件的距離
    margin: const EdgeInsets.all(10.0),
    // decoration: 用於設定背景色、邊框、圓角等複雜樣式
    decoration: BoxDecoration(
      color: Colors.amber.shade100, // 背景色
      border: Border.all(color: Colors.black, width: 3), // 邊框
      borderRadius: BorderRadius.circular(12), // 圓角
    ),
    child: const Text('This is a Container'),
  ),
),

就會看到如下圖,有背景、邊框、有圓角的黃色方塊。讓我們試著調整 paddingmargin 的數值,感受它們的差異。

Container

Step 3: ColumnRow - 佈局的靈魂

單一的 Container 不足以構成複雜畫面,我們需要 ColumnRow 來組織多個 Widget。

核心觀念:主軸 (Main Axis) 與 交錯軸 (Cross Axis)

  • Column (垂直排列):

    • 主軸 (Main Axis) 是 垂直 方向 (由上到下)。
    • 交錯軸 (Cross Axis) 自然就是水平方向 (由左到右)。
  • Row (水平排列):

    • 主軸 (Main Axis) 是 水平 方向 (由左到右)。
    • 交錯軸 (Cross Axis) 自然就是垂直方向 (由上到下)。

透過 mainAxisAlignment 和 crossAxisAlignment 這兩個屬性來控制子元件在這兩個軸線上的對齊方式。

Step 4: 動手實作 SnapSaver 靜態畫面

我們希望的佈局是:頂部一個總覽區塊,中間是主要功能按鈕。

HomePagebody 替換成以下程式碼:

body: Column(
  // Column 的主軸是垂直的,我們希望內容從頂部開始
  mainAxisAlignment: MainAxisAlignment.start,
  // Column 的交錯軸是水平的,我們希望內容撐滿寬度
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [ // children 屬性接收一個 Widget 列表
    // 1. 頂部總覽區塊
    Container(
      padding: const EdgeInsets.all(24.0),
      margin: const EdgeInsets.all(16.0),
      decoration: BoxDecoration(
        color: Colors.grey.shade200,
        borderRadius: BorderRadius.circular(10),
      ),
      child: const Column(
        // 這個內部的 Column,我們希望文字靠左對齊
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '本月總支出',
            style: TextStyle(fontSize: 16, color: Colors.black54),
          ),
          Text(
            'NT\$ 12,345',
            style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
          ),
        ],
      ),
    ),

    // 2. 中間功能按鈕區塊
    Container(
      margin: const EdgeInsets.symmetric(horizontal: 16.0),
      child: Row(
        // Row 的主軸是水平的,我們希望按鈕之間平均分佈空間
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          // 第一個按鈕
          Column(
            children: [
              Icon(Icons.camera_alt, size: 40, color: Colors.deepPurple),
              Text('掃描發票'),
            ],
          ),
          // 第二個按鈕
          Column(
            children: [
              Icon(Icons.edit, size: 40, color: Colors.deepPurple),
              Text('手動記帳'),
            ],
          ),
          // 第三個按鈕
          Column(
            children: [
              Icon(Icons.history, size: 40, color: Colors.deepPurple),
              Text('查看紀錄'),
            ],
          ),
        ],
      ),
    ),
  ],
),

排列

程式碼解析

  1. 最外層的 Column,負責將「總覽區塊」和「功能按鈕區塊」由上到下排列。
  2. 「總覽區塊」使用 Container 來設定背景色和邊距,內部再用一個 Column 來排列兩行 Text
  3. 「功能按鈕區塊」使用 Row 來將三個按鈕由左到右排列。mainAxisAlignment.spaceAround 讓它們在水平方向上均勻分佈。
  4. 每一個按鈕本身,又是一個小 Column,負責將 IconText 上下排列。

透過一層層的 Widget 嵌套,像堆樂高一樣,直觀地描述出你想要的畫面。

結構: 將這個畫面的佈局視覺化成以下的樹狀結構

Column
└── children:
    ├── Container (總覽區塊)
    │   └── child: Column
    │       └── children:
    │           ├── Text ('本月總支出')
    │           └── Text ('NT$ 12,345')
    └── Container (功能按鈕區塊)
        └── child: Row
            └── children:
                ├── Column (掃描發票)
                │   ├── Icon
                │   └── Text
                ├── Column (手動記帳)
                │   ├── Icon
                │   └── Text
                └── Column (查看紀錄)
                    ├── Icon
                    └── Text

今日結語

今天我們告別了範例程式碼,從一張白紙親手搭建了 App 的第一個畫面。透過掌握 Container 的用法,並徹底理解 ColumnRow 的主軸與交錯軸概念,我們已經為未來打造任何複雜的佈局,打下了最堅實的基礎。


上一篇
Day 4: 創建「省錢拍拍」、解構 Flutter App 的骨架
下一篇
Day 6: 讓畫面動起來 - ListView 與 Card 的列表魔法
系列文
攜手 AI 從零開始打造一款 Flutter 應用程式8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言